package com.accedo.achievements.services.ajax.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.accedo.achievements.dataaccess.dao.impl.AchievementDaoImpl;
import com.accedo.achievements.dataaccess.dao.impl.AppDaoImpl;
import com.accedo.achievements.dataaccess.dao.impl.BadgeDaoImpl;
import com.accedo.achievements.dataaccess.dao.impl.CampaignDaoImpl;
import com.accedo.achievements.dataaccess.dao.impl.UserDaoImpl;
import com.accedo.achievements.dataaccess.model.Achievement;
import com.accedo.achievements.dataaccess.model.User;
import com.accedo.achievements.services.ajax.AchievementService;

/**
 * The business logic service of achievement relevant functionality.
 * @author Bo Liang
 */
@Component
public class AchievementServiceImpl implements AchievementService {
	
	private static final Logger log = LoggerFactory.getLogger(AchievementServiceImpl.class);
	
	private Achievement achi; //injection
	private User user; //injection
	
	private AchievementDaoImpl achiDao; //injection
	private UserDaoImpl userDao; //injection
	private BadgeDaoImpl badgeDao; //injection
	private CampaignDaoImpl campDao; //injection
	private AppDaoImpl appDao; //injection

	// Constructor
	AchievementServiceImpl() {};
	
	/**
	 * Get the specific achievement's name, score and description by using achievement serial number
	 * @param achi_sn	achievement serial number
	 * @return			JSON format achievement name, score and description<br>
	 * eg. {achi_desc:'this is the achi001 description!', achi_name:'Finish stage 1', achi_score:10}
	 */
	public JSONObject getJsonAchiByAchisn(String achi_sn) {
		log.debug("### Entering getJsonAchiByAchisn(achi_sn = {})", achi_sn);
		JSONObject jsonObject = new JSONObject(); 
		achi = achiDao.getAchiByAchisn(achi_sn); 
		jsonObject.element("achi_desc", achi.getAchi_desc());
		jsonObject.element("achi_name", achi.getAchi_name());
		jsonObject.element("achi_score", achi.getAchi_score());
		log.debug("### Leaving getJsonAchiByAchisn() : {}", jsonObject);
		return jsonObject; 
	}
	
	/**
	 * The function deals with the unlocking event and relevant events about badges and campaigns.
	 * <p>after user trigger the unlocking of achievement.
	 * Firstly, add this achievement to the user, if this user has unlocked this achievement before then return,
	 * if it's the first time this user unlocked this achievement, then add the points of this achievement to user,
	 * and invoke badges and campaigns events which includes this achievement.
	 * @param username	user name
	 * @param achi_sn	achievement serial number
	 * @return			JSON format popup<br>
	 * eg. {achi_name:'Finish stage 1', card_sn:'CARD001'}
	 */
	public String unlockAndJsonPopup(String username, String achi_sn) {	
		log.debug("### Entering unlockAndJsonPopup(username = {}, achi_sn = {})", username, achi_sn);
		achi = achiDao.getAchiByAchisn(achi_sn); 
		user = userDao.getUserByUsername(username); 
		// add the achievement to user's Set
		String flag = userDao.addAchiToUser(username, achi_sn); 
		
		// if this achievement is unlocked first time by this user
		if (flag.equals("new")) {
			// add this achievement's score to user
			userDao.addScoreToUser(username, achi.getAchi_score());
			
			List<Integer> badgeIdList = new ArrayList<Integer>(); 
			badgeIdList = badgeDao.getBadgeIdListByAchiId(achi.getId()); 
			
			// badges which related to this achievement, add the relevant number of badge increased
			for (Iterator<Integer> iter = badgeIdList.iterator(); iter.hasNext(); ) {
				int badge_id = iter.next(); 
				userDao.addAchiUnlockedNumForUserBadge(username, badge_id); 
			}
			
			List<Integer> campIdList = new ArrayList<Integer>(); 
			campIdList = campDao.getCampIdListByAchiId(achi.getId()); 
			
			// campaigns which related to this achievement, add the relevant number of campaign increased
			for (Iterator<Integer> iter = campIdList.iterator(); iter.hasNext(); ) {
				int camp_id = iter.next(); 
				userDao.addAchiUnlockedNumToUserCamp(username, camp_id); 
			}
		}
		String jsonPopup = "{achi_name:'" + achi.getAchi_name() + "', card_sn:'"+ achi.getCard_sn() + "'}";
		
		log.debug("### Leaving unlockAndJsonPopup() : {}", jsonPopup);
		return jsonPopup;
	}
	
	/**
	 * Get achievements by application sn and user name with two states 'A' and 'B'.
	 * <p>state 'A' means this achievement user has already unlocked,
	 * state 'B' means this achievements this user has not unlocked yet.
	 * @param username	user name
	 * @param app_sn	application serial number
	 * @return			JSON format achievements<br>
	 * eg. {achis:[{achi_sn:'ACHI00101',achi_name:'Finish stage 1',achi_score:'10',achi_state:'A'},
	 * {achi_sn:'ACHI00102',achi_name:'Get 2 magic stones',achi_score:'20',achi_state:'A'}]}
	 */
	public JSONObject getJsonAchisByAppsn(String username, String app_sn) {
		log.debug("### Entering getJsonAchisByAppsn(username = {}, app_sn = {})", username, app_sn);
		JSONObject jsonObject = new JSONObject();
		List<Achievement> achiList = new ArrayList<Achievement>(); 
		List<String> achiStringList = new ArrayList<String>(); 
		Set<Achievement> achiSet = new HashSet<Achievement>(); 
		JSONArray jsonArray = new JSONArray();
		
		// get user's unlocked achievements
		if (username != null) {
			achiSet = userDao.getUserByUsername(username).getAchis();
		}
		
		// let user's unlocked achievements' serial number into List
		for (Iterator<Achievement> iter = achiSet.iterator(); iter.hasNext();) {
			achi = iter.next();
			achiStringList.add(achi.getAchi_sn());
		}
		 
		achiList = achiDao.getAchiListByAppsn(app_sn); 
		for(Iterator<Achievement> iter = achiList.iterator(); iter.hasNext(); ) {
			achi = iter.next(); 
			
			String achiComp = null;
			// if the achievements sn is included in the list, make the achiComp to A
			if (achiStringList.contains(achi.getAchi_sn())) {
				achiComp = "A";
			} else {
				achiComp = "B";
			}
	
			jsonObject.element("achi_sn", achi.getAchi_sn()); 
			jsonObject.element("achi_name", achi.getAchi_name()); 
			jsonObject.element("achi_score", achi.getAchi_score()); 
			jsonObject.element("achi_state", achiComp); 
			jsonArray.add(jsonObject);
			jsonObject.clear();
			
		}
		
		jsonObject.element("achis", jsonArray); 
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		log.debug("### Leaving getJsonAchisByAppsn() : {}", jsonObject);
		return jsonObject; 
	}
	
	/**
	 * Return the achievements list with whether user unlocked or not, that belongs to the badge.
	 * <p>badge includes batch of achievements.<br>
	 * This function return the name of achievement and the application it belongs to, and state of the user whether unlocked it or not.
	 * @param badge_sn	badge serial number
	 * @param username	user name
	 * @return			JSON format badgeAchis<br>
	 * eg. {badgeAchis:[{app_sn:'APP001', app_name:'Hungry Harry I', achi_sn:'ACHI00102',
	 *  achi_name:'Get 2 magic stones', state:'true'}]}
	 */
	public JSONObject getJsonAchisByBadgesnAndUsername(String badge_sn, String username) {
		log.debug("### Entering getJsonAchisByBadgesnAndUsername(badge_sn = {}, username = {})", badge_sn, username);
		JSONObject jsonObject = new JSONObject();
		List<String> achiSnList = new ArrayList<String>(); 
		Set<Achievement> achiSet = new HashSet<Achievement>(); 
		JSONArray jsonArray = new JSONArray();
		
		// get all achievements belongs to the badge
		achiSet = achiDao.getAchiListByBadgesn(badge_sn); 
		user = userDao.getUserByUsername(username); 
		
		// get user's unlocked achievements sn into achievement sn list
		for (Iterator<Achievement> iterUser = user.getAchis().iterator(); iterUser.hasNext(); ) {
			achiSnList.add(iterUser.next().getAchi_sn()); 
		}
		
		for (Iterator<Achievement> iter = achiSet.iterator(); iter.hasNext(); ) {
			achi = iter.next(); 
			String app_sn = achi.getApp_sn(); 
			String app_name = appDao.getAppByAppsn(app_sn).getApp_name();
			String state = "false"; 
			// if this achievement belongs the user's unlocked achievement list, assign the start in true
			if (achiSnList.contains(achi.getAchi_sn())) {
				state = "true";
			}
			jsonObject.element("app_sn", app_sn);
			jsonObject.element("app_name", app_name);
			jsonObject.element("achi_sn", achi.getAchi_sn());
			jsonObject.element("achi_name", achi.getAchi_name());
			jsonObject.element("state", state);
			jsonArray.add(jsonObject);
			jsonObject.clear();
			
		}
		jsonObject.element("badgeAchis", jsonArray);
		
		log.debug("### Leaving getJsonAchisByBadgesnAndUsername() : {}", jsonObject);
		return jsonObject; 
	}
	
	/**
	 * Return the achievements belongs to the campaign with two state, true and false.
	 * <p>this function return the achievements of the specific campaign,
	 * and with two states based on user.
	 * That 'true' means the user has already unlocked the achievement,
	 * 'false' means the user has not unlocked it yet.
	 * @param camp_sn	campaign serial number
	 * @param username	user name
	 * @return			JSON format campAchis<br>
	 * eg. {campAchis:[{app_sn:'APP003', app_name:'Black Jack Pirates I', achi_sn:'ACHI00303',
	 *  achi_name:'Register email', state:'false'}]}
	 */
	public JSONObject getJsonAchisByCampsnAndUsername(String camp_sn, String username) {
		log.debug("### Entering getJsonAchisByCampsnAndUsername(camp_sn = {}, username = {})", camp_sn, username);
		JSONObject jsonObject = new JSONObject();
		List<String> achiSnList = new ArrayList<String>(); 
		Set<Achievement> achiSet = new HashSet<Achievement>(); 
		JSONArray jsonArray = new JSONArray();
		
		// get all achievements belongs to the campaign
		achiSet = achiDao.getAchiListByCampsn(camp_sn); 
		user = userDao.getUserByUsername(username); 
		
		// add user's unlocked achievements sn into achievement sn list
		for (Iterator<Achievement> iterUser = user.getAchis().iterator(); iterUser.hasNext(); ) {
			achiSnList.add(iterUser.next().getAchi_sn()); 
		}
		
		for (Iterator<Achievement> iter = achiSet.iterator(); iter.hasNext(); ) {
			achi = iter.next(); 
			String app_sn = achi.getApp_sn(); 
			String app_name = appDao.getAppByAppsn(app_sn).getApp_name();
			String state = "false"; 
			// if this achievement belongs the user's unlocked achievement list, assign the state with true
			if (achiSnList.contains(achi.getAchi_sn())) {
				state = "true";
			}
			jsonObject.element("app_sn", app_sn); 
			jsonObject.element("app_name", app_name); 
			jsonObject.element("achi_sn", achi.getAchi_sn()); 
			jsonObject.element("achi_name", achi.getAchi_name()); 
			jsonObject.element("state", state); 
			jsonArray.add(jsonObject); 
			jsonObject.clear(); 
			
		}
		jsonObject.element("campAchis", jsonArray); 
		
		log.debug("### Leaving getJsonAchisByCampsnAndUsername() : {}", jsonObject);
		return jsonObject; 
	}
	
	public UserDaoImpl getUserDao() {
		return userDao;
	}

	@Resource
	public void setUserDao(UserDaoImpl userDao) {
		this.userDao = userDao;
	}

	public User getUser() {
		return user;
	}

	@Resource
	public void setUser(User user) {
		this.user = user;
	}

	public Achievement getAchi() {
		return achi;
	}

	@Resource
	public void setAchi(Achievement achi) {
		this.achi = achi;
	}

	public AchievementDaoImpl getAchiDao() {
		return achiDao;
	}

	@Resource
	public void setAchiDao(AchievementDaoImpl achiDao) {
		this.achiDao = achiDao;
	}

	public BadgeDaoImpl getBadgeDao() {
		return badgeDao;
	}

	@Resource
	public void setBadgeDao(BadgeDaoImpl badgeDao) {
		this.badgeDao = badgeDao;
	}

	public CampaignDaoImpl getCampDao() {
		return campDao;
	}

	@Resource
	public void setCampDao(CampaignDaoImpl campDao) {
		this.campDao = campDao;
	}
	
	
	public AppDaoImpl getAppDao() {
		return appDao;
	}

	@Resource
	public void setAppDao(AppDaoImpl appDao) {
		this.appDao = appDao;
	}
}
